/* Copyright 2008. All rights reserved. */
package org.bling.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.antlr.runtime.tree.Tree;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.bling.language.BlingParser;

/**
 * <code>Node</code> -  XML documents are converted into an ANTLR syntax tree,
 * which is then converted into a screen graph of Nodes.  These nodes are
 * representations of the screen in abstract non-SWT objects.
 *
 * @author DISBROB
 * 
 */
public class Node {

	private static final Logger LOG = LogManager.getLogger(Node.class);
	Integer type;
	String name;

	Map<String, String> attributes = new HashMap<String, String>();
	List<Node> children = new ArrayList<Node>();
	Node parent;

	public Node() {

	}

	public Node(Tree tree) {
		this(null, tree);
		this.type = new Integer(tree.getType());
	}

	public Node(Node parent, Tree tree) {
		this.parent = parent;
		convert(tree);
		if (parent != null) {
			parent.addChild(this);
		}
		this.type = new Integer(tree.getType());
	}

	public List<Node> getChildren() {
		return children;
	}

	public Map<String, String> getAttributes() {
		return attributes;
	}
	
	public int getNumberOfChildren() {
		return children.size();
	}

	public void addChild(Node child) {
		children.add(child);
	}

	private void convert(Tree tree) {
		switch (tree.getType()) {
		case BlingParser.TAG_OPENING:
			LOG.debug("--parse tag opening: " + tree);
			convertTagOpenning(tree);
			break;
		case BlingParser.TAG_SELF_CLOSING:
			LOG.debug("--parse self closing: " + tree);
			convertTagSelfClosing(tree);
			break;
		case BlingParser.TAG_CLOSING:
			LOG.debug("--parse tag closing: " + tree);
			convertTagClosing(tree);
			break;
		case BlingParser.TEXT:
			LOG.debug("--parse text: " + tree);
			convertText(tree);
			break;
		case BlingParser.ROOT:
			LOG.debug("--parse root: " + tree);
			convertRootNode(tree);
			break;
		default:
			throw new RuntimeException(
					"ERROR: Only support for Tags - unknown: " + tree.getText());
		}
	}

	/**
	 * @param tree
	 */
	private void convertRootNode(Tree tree) {
		Stack<Node> tags = new Stack<Node>();
		for (int i = 0; i < tree.getChildCount(); i++) {
			Tree child = tree.getChild(i);

			// Figure out who the parent should be
			Node parent = this;
			if (tags.size() > 0) {
				parent = tags.peek();
			}

			if (child.getType() == BlingParser.TAG_OPENING) {
				Node tag = new Node(parent, child);
				tags.push(tag);
			} else if (child.getType() == BlingParser.TAG_SELF_CLOSING) {
				Node tag = new Node(parent, child);
			} else if (child.getType() == BlingParser.TAG_CLOSING) {
				Node current = tags.peek();
				if (child.getChild(0).getText().equals(current.getName())) {
					tags.pop();
				} else {
					throw new RuntimeException(
							"closing tags must be out of order. Expecting "
									+ current.getName() + " but found "
									+ child.getText() + " on line "
									+ child.getLine());
				}
			} else {
				Node n = new Node(parent, child);
			}
		}

		if (tags.size() != 0) {
			throw new RuntimeException(
					"one or more of the tags did not close: " + tags);
		}
	}

	/**
	 * @param tree
	 */
	private void convertText(Tree tree) {
		String actualText = "";
		for (int i = 0; i <= tree.getChildCount() - 1; i++) {
			String buffer = tree.getChild(i).getText();
			if (actualText == "") {
				actualText = buffer;
			} else {
				actualText += " " + buffer;
			}
		}
		name = actualText;
	}

	/**
	 * @param tree
	 */
	private void convertTagClosing(Tree tree) {
		name = tree.getChild(0).getText();
	}

	/**
	 * @param tree
	 */
	private void convertTagSelfClosing(Tree tree) {
		// NOTE: Currently, self closing tags are modeled the
		// same as opening tags. There are no special flags
		// to indicate a self closing type, there will simply
		// be no children assigned to the tag. Self closing
		// tags have a name, and attributes, just like opening
		// tags. The distinction is such that the root tag
		// knows how to parse & convert the two different tags.
		convertTagOpenning(tree);
	}

	/**
	 * @param tree
	 */
	private void convertTagOpenning(Tree tree) {
		name = tree.getChild(0).getText();

		for (int i = 1; i < tree.getChildCount(); i++) {
			Tree child = tree.getChild(i);
			if (child.getType() == BlingParser.TAG_ATTRIBUTE) {
				// FIXME: Bounds checking 0 & 1
				String key = child.getChild(0).getText();
				String value = child.getChild(1).getText();
				value = value.substring(1, value.length() - 1);
				attributes.put(key, value);
			} else {
				Node n = new Node(this, child);
				children.add(n);
			}
		}
	}

	public Node getParent() {
		return parent;
	}

	public String getName() {
		return name;
	}

	@Override
	public String toString() {
		return "#" + type + "#" + " " + name + " " + attributes;
	}

	private int depth() {
		int depth = 0;
		Node traverse = parent;
		while (traverse != null) {
			traverse = traverse.parent;
			depth++;
		}
		return depth;
	}

	public StringBuffer prettyPrint() {
		StringBuffer sb = new StringBuffer();
		for (int i = 1; i <= depth(); i++) {
			sb.append("\t");
		}

		sb.append(toString() + "\n");

		Iterator<Node> kids = children.iterator();
		while (kids.hasNext()) {
			Node child = kids.next();
			sb.append(child.prettyPrint());
		}
		return sb;
	}
}
